//Modifies.cpp
//#include "Modifies.h"
#include "PKB.h"
//#include <iostream>

Modifies::Modifies() { debug = false; }


Modifies::~Modifies() {
//	this->modifies_list.clear();
}

/*void Modifies::InitializeModifies(int num_of_statements) {
    // debugging mode
	this->debug = true;
	this->num_of_statements = num_of_statements;
}
*/


void Modifies::InsertModifies(STMT statement, VARINDEX var_index, PROCINDEX proc_index) {
	assert(var_index >= 0 && "var_index must be >= 0");
	assert(statement.statement_number > 0 && "statement number must be > 0");

	if(debug == false) {
		assert(PKB::varTable.GetVarName(var_index) != "");
		assert(PKB::procTable.GetProcName(proc_index) != "");
	}
	
	// 1. Fill var -> stmts association
	if(this->var_stmts.find(var_index) != var_stmts.end()) {
		this->var_stmts[var_index].insert(statement);
	}
	else {
		STMT_ARRAY new_stmts_entry;
		new_stmts_entry.insert(statement);
		this->var_stmts.insert(pair<VARINDEX,STMT_ARRAY>(var_index, new_stmts_entry));
	}

	// 2. Fill stmt->vars association
	auto finder_v = this->stmt_vars.find(statement.statement_number);
	if(finder_v != stmt_vars.end()) {
		finder_v->second.insert(var_index);
	}
	else {
		VARINDEX_ARRAY new_vars_entry;
		new_vars_entry.insert(var_index);
		stmt_vars.insert(pair<int, VARINDEX_ARRAY>(statement.statement_number, new_vars_entry));
	}

	// 3. Fill var -> procs 
	if (this->var_procs.find(var_index) != var_procs.end()) {
		this->var_procs[var_index].insert(proc_index);
	}
	else {
		PROCINDEX_ARRAY new_procs;
		new_procs.insert(proc_index);
		this->var_procs.insert(pair<VARINDEX, PROCINDEX_ARRAY>(var_index, new_procs));
		
	}

	// 4. Fill proc -> vars
	auto finder_p = this->proc_vars.find(proc_index);
	if (finder_p != proc_vars.end()) {
		finder_p->second.insert(var_index);
	}
	else {
		VARINDEX_ARRAY new_vars;
		new_vars.insert(var_index);
		proc_vars.insert(pair<PROCINDEX, VARINDEX_ARRAY>(proc_index, new_vars));
	}
}



STMT_ARRAY Modifies::GetAssignmentModifiesVariable(VARINDEX var_index) {

	if (debug == false) {
		assert(PKB::varTable.GetVarName(var_index) != "");
	}

	STMT_ARRAY sub_result_set, result_set;
	
	sub_result_set = this->var_stmts[var_index];

	// Do filter 
	for_each(sub_result_set.begin(), sub_result_set.end(), [&result_set, this](STMT statement) {
		// CHECK IF IT IS A WHILE STATEMENT
		statement.node_type = WHILE;
		if((!debug && (!PKB::parent.IsParent(statement))) || (debug && (!parent_test.IsParent(statement)))) {
			statement.node_type = ASSIGN;
			result_set.insert(statement);
		}
		// CHECK IF IT IS AN IF STATEMENT
		statement.node_type = IF;
		if((!debug && (!PKB::parent.IsParent(statement))) || (debug && (!parent_test.IsParent(statement)))) {
			statement.node_type = ASSIGN;
			result_set.insert(statement);
		}
	});

	
	return result_set;

}



VARINDEX Modifies::GetVariableModifiedByAssignment(STMT assignment) {
	assert(assignment.statement_number > 0);
			
	return *(stmt_vars[assignment.statement_number].begin());
}




BOOLEAN Modifies::GetAssignmentModifies(STMT statement, VARINDEX var_index) {

	assert(statement.statement_number > 0 && "statement number must be > 0");
	// Given an assignment statement, we want to see if it modifies var_index
	return (this->stmt_vars[statement.statement_number].find(var_index) != this->stmt_vars[statement.statement_number].end());
}


BOOLEAN Modifies::GetContainerModifies(STMT statement, VARINDEX var_index, NODETYPE node_type) 
{

	//STMT_ARRAY sub_result;
	VARINDEX_ARRAY sub_result;
	
	/*sub_result = this->GetContainerModifiesVariable(var_index, node_type);
	for (auto iter=sub_result.begin(); iter!=sub_result.end(); iter++) {
		if (statement.statement_number == (*iter).statement_number)
			return true;
	}*/
	sub_result = this->GetVariablesModifiedByContainer(statement);
	return (sub_result.find(var_index) != sub_result.end());


//	return false;
	
}

BOOLEAN Modifies::GetStatementModifies(STMT statement, VARINDEX var_index)
{
	STMT_ARRAY sub_result;
	bool assign_check, if_check, while_check, call_check;
	call_check = false;
	
	assign_check = this->GetAssignmentModifies(statement, var_index);
	if_check = this->GetContainerModifies(statement, var_index, IF);
	while_check = this->GetContainerModifies(statement, var_index, WHILE);

	PROCINDEX called_proc = PKB::calls.GetCalleeByStatement(statement.statement_number);
	if (called_proc > 0) {
		VARINDEX_ARRAY modified_vars = this->GetVariablesModifiedByProcedure(called_proc);
		call_check = (modified_vars.find(var_index) != modified_vars.end());
	} 

	
	return (assign_check || if_check || while_check || call_check);

}

BOOLEAN Modifies::GetProcedureModifies(PROCINDEX proc_index, VARINDEX var_index) 
{
	VARINDEX_ARRAY result_set = this->GetVariablesModifiedByProcedure(proc_index);
	return (result_set.find(var_index) != result_set.end());
}


STMT_ARRAY Modifies::GetContainerModifiesVariable(VARINDEX var_index, NODETYPE node_type) {
	assert(var_index >= 0);
	assert(node_type == IF || node_type == WHILE);
	
	STMT_ARRAY children = GetAssignmentModifiesVariable(var_index); 
	// for each element in temp, recursively search for their ancestors.
	return IdentifyParents(children, node_type, 0);
}



STMT_ARRAY Modifies::IdentifyParents(STMT_ARRAY children, NODETYPE node_type,  int r_n) {
	unsigned int old_size  = children.size();
	STMT_ARRAY temp_array;
	STMT_ARRAY parent_array;
	
	// only want to store results from 2nd recursion onwards.
	if(r_n > 0)
		temp_array = children;

	// retrieve Parent* of each child statement
	for (auto iter_child = children.begin(); iter_child!=children.end(); iter_child++) {
		debug ? 
			parent_array = parent_test.RetrieveTransitiveParent((*iter_child), node_type) 
			:  parent_array = PKB::parent.RetrieveTransitiveParent((*iter_child), node_type); // get all parents
		
		
		set_union(temp_array.begin(), temp_array.end(), parent_array.begin(), parent_array.end(), inserter(temp_array, temp_array.begin()));
		
	}
	// termination conditions
	if (old_size == temp_array.size()) // if no more new parents found
		return temp_array; 
	else // find more
		return IdentifyParents(temp_array, node_type, r_n+1);

}

VARINDEX_ARRAY Modifies::GetVariablesModifiedByContainer(STMT container){
	VARINDEX_ARRAY result_set;

	STMT_ARRAY all_child;

	// 1. Fetch all children of the container statement
	debug ? all_child = parent_test.GetChild(container) : 
		all_child = PKB::parent.GetChild(container);
	
	//int child_statement_num = -1;
	
	for(auto iter = all_child.begin(); iter != all_child.end(); iter++) {
		int child_statement_num = (*iter).statement_number;
		
		VARINDEX_ARRAY temp_set;
		switch((*iter).node_type) {
		case ASSIGN: {
			VARINDEX modified_variable = GetVariableModifiedByAssignment(*iter);
			result_set.insert(modified_variable);
		} break;
		case IF:
		case WHILE: {
			temp_set = GetVariablesModifiedByContainer(*iter);
			set_union(result_set.begin(), result_set.end(), temp_set.begin(), temp_set.end(), inserter(result_set, result_set.begin()));
		} break;
		case CALL: {
			// get procedure that is called and fetch variables that are modified by the procedure
			PROCINDEX proc_index_of_child = PKB::calls.GetCalleeByStatement((*iter).statement_number);
			temp_set = GetVariablesModifiedByProcedure(proc_index_of_child);
			set_union(result_set.begin(), result_set.end(), temp_set.begin(), temp_set.end(), inserter(result_set, result_set.begin()));
		} break;
		default: break;
		} // end switch
	}
	return result_set;
}


PROCINDEX_ARRAY Modifies::GetProceduresModifiesVariable(VARINDEX var_index) {			
	PROCINDEX_ARRAY temp_set, caller_array, direct_modifiers, proc_array;

	direct_modifiers = var_procs[var_index];

	caller_array = direct_modifiers;
	// for each direct modifier (procedure), we fetch
	// the callers as they are deemed as valid candidates
	// to fulfil the relation

	for (auto p_index = direct_modifiers.begin(); p_index!=direct_modifiers.end(); p_index++) {

		// fetch transitive callers
		temp_set = PKB::calls.RetrieveTransitiveCaller(*p_index);
		set_union(caller_array.begin(), caller_array.end(),
			temp_set.begin(), temp_set.end(),
			inserter(caller_array, caller_array.begin()));
	}

	return caller_array;
}

VARINDEX_ARRAY Modifies::GetVariablesModifiedByProcedure(PROCINDEX proc_index) {
	
	PROCINDEX_ARRAY callee_array;
	VARINDEX_ARRAY modified_vars;
	
	modified_vars = proc_vars[proc_index];

	callee_array = PKB::calls.RetrieveTransitiveCallee(proc_index);

	for (auto p_index=callee_array.begin(); p_index!=callee_array.end(); p_index++) {
		set_union(modified_vars.begin(), modified_vars.end(), 
			proc_vars[*p_index].begin(), proc_vars[*p_index].end(),
			inserter(modified_vars, modified_vars.begin()));
	}

	return modified_vars;
}

